#include "stdafx.h"
#include "IppH264Decoder.h"
#include <algorithm>
#include <set>



//-----------------------------Constant Definition------------------------------




//----------------------------Data Type Definition------------------------------

//the h264 decode session idenfier type
typedef void * H264DECSESSION;

#ifdef _WINDLL
#define APISPEC(result_type) extern "C" __declspec(dllexport) result_type __cdecl
#else
#define APISPEC(result_type) extern "C" result_type __cdecl
#endif



//----------------------------Ipp H264 Decoder Manager--------------------------

class DecoderManager {
private:
	Util::CritSec              m_lock;
	std::set<IppH264Decoder *> m_allDecoders;

	void AddDecoder(IppH264Decoder *dec)
	{
		Util::AutoLock lock(m_lock);
		
		m_allDecoders.insert(dec);
	}

	void RemoveDecoder(IppH264Decoder *dec)
	{
		Util::AutoLock lock(m_lock);

		m_allDecoders.erase(dec);
	}

public:
	DecoderManager(void) {}

	~DecoderManager(void)
	{
		Clear();
	}

	IppH264Decoder *HandleToDecoder(H264DECSESSION handle)
	{
		IppH264Decoder *dec = reinterpret_cast<IppH264Decoder *>(handle);
		return (m_allDecoders.find(dec) != m_allDecoders.end()) ? dec : (IppH264Decoder *) NULL;
	}

	IppH264Decoder *CreateDecoder(unsigned int /*width*/, unsigned int /*height*/, bool /*isStreamBased*/)
	{
		IppH264Decoder *dec = new IppH264Decoder();
		if (dec == NULL)
			return NULL;

		AddDecoder(dec);

		return dec;
	}

	void DestroyDecoder(H264DECSESSION handle)
	{
		IppH264Decoder *dec = HandleToDecoder(handle);
		if (dec == NULL)
			return;

		RemoveDecoder(dec);
		delete dec;
	}

	void Clear(void)
	{
		Util::AutoLock lock(m_lock);

		std::for_each(m_allDecoders.begin(), m_allDecoders.end(), Util::DeleteObject<IppH264Decoder>);
		m_allDecoders.clear();
	}

};

static DecoderManager s_decMgr;



//-------------------------Forward Declaration----------------------------------





//-------------------------H264 Decode SDK API Implement------------------------

//Create a new h264 decode session with the specific video size.
//parameters:
//streambased - set to non-zero if the data send to the decoder is a bitstream not frames
//return value:
//a non-zero H264DECSESSION value if a new decode session was created,
//or 0 if the video size is invalid.
APISPEC(H264DECSESSION) CreateDecodeSession(unsigned int width, unsigned int height, int streambased)
{
	return (H264DECSESSION) s_decMgr.CreateDecoder(width, height, (streambased != 0));
}

//Create a new h264 decode session,the video size will be decided by the decoder while decoding.
//parameters:
//streambased - set to non-zero if the data send to the decoder is a bitstream not frames
//return value:
//a non-zero H264DECSESSION value if a new decode session was created,
//or 0 if failed.
APISPEC(H264DECSESSION) CreateDecodeSessionNoSize(int streambased)
{
	return CreateDecodeSession(0, 0, streambased);
}

//Close a decode session,all resources of the session will be released.
APISPEC(void) CloseDecodeSession(H264DECSESSION session)
{
	s_decMgr.DestroyDecoder(session);
}

//Close all decode sessions.
APISPEC(void) ClearAllSession()
{
	s_decMgr.Clear();
}

//get video size relative to the decode session.
//parameters:
//session - the session identiier
//width - address of a integer value for the output video width 
//height - address of a integer value for the output video height 
//return value:
//0 - the video size of the session has been set.
//-1 - invalid session
//-2 - the video size has not been set yet.
//-3 - at least one of the parameters for output is null
APISPEC(int) GetVideoSize(H264DECSESSION session, int *width, int *height)
{
	IppH264Decoder *decoder = s_decMgr.HandleToDecoder(session);

	if (decoder == NULL)
		return -1;

	return decoder->GetDecodedVideoSize(*(uint32_t *) width, *(uint32_t *) height);
}


//Decode h264 stream data,the output format is yuv420p.
//parameters:
//session - the session identiier
//inbuf - the address of h264 stream data to be decoded
//size - the size of h264 video stream data
//getframe - address to a integer value,the value will be set to non-zero if a new frame is decoded during this call,or set to 0 if still need more data for a new frame
//ybuf - the address to output buffer of y channel of decoded frame.it must not be null.
//ubuf - the address to output buffer of u channel of decoded frame it must not be null.
//vbuf - the address to output buffer of v channel of decoded frame it must not be null.
//dstlinesize - the output buffer line size.
//              you may set a different line size which must be larger than width of frame,or 0 to use the width of frame.
//              the line size of ubuf and vbuf will be the half of it.
//reverse - set to non-zero if you want to output the decoded frame upside down,or else set to 0.
//
//Return value:
//a positive integer indicates how many bytes was decoded if the frame was decoded successfully,
//or a negative integer error number in the following list.Check getframe to see if a new frame decoded.
//-1 - can not find the decode session width the specific H264DECSESSION value.
//-2 - encounters an error while decoding
//-4 - the dstlinesize small than frame width
//-5 - one of the output buffer addresses is null
//-6 - the address to h264 frame data is null,or size is 0.
//-7 - getframe is a null pointer
APISPEC(int) DecodeData(H264DECSESSION session, unsigned char *inbuf, unsigned int size, 
							   int *getframe,
							   unsigned char *ybuf, unsigned char * /*ubuf*/, unsigned char * /*vbuf*/,
							   int dstlinesize, int /*reverse*/)
{
	IppH264Decoder *decoder = s_decMgr.HandleToDecoder(session);

	if (decoder == NULL)
		return -1;

	if (ybuf == NULL)
		return -5;

	if (inbuf == NULL)
		return -6;

	bool gotNewFrame = false;

	uint32_t width, height, bufSize;
	if (decoder->GetDecodedVideoSize(width, height) != 0)
		bufSize = 0;
	else 
		bufSize = width * height * 3 / 2;
	int ret = decoder->DecodeH264Data(inbuf, size, ybuf, bufSize, gotNewFrame);

	*getframe = gotNewFrame ? 1 : 0;

	//{
	//	char buf[128];
	//	if (gotNewFrame)
	//		sprintf(buf, "frame decoded %u.\n", size);
	//	else
	//		sprintf(buf, "frame can not be decoded %u.\n", size);
	//	OutputDebugString(buf);
	//}

	return ret;
}

//Decode a h264 frame,the output format is yuv420p.
//parameters:
//session - the session identiier
//inbuf - the address of h264 video frame data
//size - the size of h264 video frame data
//ybuf - the address to output buffer of y channel of decoded frame.it must not be null.
//ubuf - the address to output buffer of u channel of decoded frame it must not be null.
//vbuf - the address to output buffer of v channel of decoded frame it must not be null.
//dstlinesize - the output buffer line size.
//              you may set a different line size which must be larger than width of frame,or 0 to use the width of frame.
//              the line size of ubuf and vbuf will be the half of it.
//reverse - set to non-zero if you want to output the decoded frame upside down,or else set to 0.
//
//Return value:
//a positive integer indicates how many bytes was decoded if the frame was decoded successfully,
//or a negative integer error number in the following list.
//-1 - can not find the decode session width the specific H264DECSESSION value.
//-2 - encounters an error while decoding
//-3 - the input data not a full h264 frame
//-4 - the dstlinesize small than frame width
//-5 - one of the output buffer addresses is null
//-6 - the address to h264 frame data is null,or size is 0.
APISPEC(int) DecodeFrame(H264DECSESSION session, unsigned char *inbuf, unsigned int size, 
								unsigned char *ybuf, unsigned char *ubuf, unsigned char *vbuf,
								int dstlinesize, int reverse)
{
	int getFrame;
	return DecodeData(session, inbuf, size, &getFrame, ybuf, ubuf, vbuf, dstlinesize, reverse);
}



//-------------------------DLL Entry Procedure----------------------------------

#ifdef __WINDLL

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		break;

	case DLL_PROCESS_DETACH:
		break;

	default:
		break;
	}

    return TRUE;
}

#endif
